home *** CD-ROM | disk | FTP | other *** search
/ SGI Developer Toolbox 6.1 / SGI Developer Toolbox 6.1 - Disc 4.iso / public / fax / src / util / AtSyntax.c++ < prev    next >
C/C++ Source or Header  |  1994-08-01  |  12KB  |  455 lines

  1. /*    $Header: /usr/people/sam/fax/util/RCS/AtSyntax.c++,v 1.6 1994/02/28 14:24:07 sam Rel $ */
  2. /*
  3.  * Copyright (c) 1993, 1994 Sam Leffler
  4.  * Copyright (c) 1993, 1994 Silicon Graphics, Inc.
  5.  *
  6.  * Permission to use, copy, modify, distribute, and sell this software and 
  7.  * its documentation for any purpose is hereby granted without fee, provided
  8.  * that (i) the above copyright notices and this permission notice appear in
  9.  * all copies of the software and related documentation, and (ii) the names of
  10.  * Sam Leffler and Silicon Graphics may not be used in any advertising or
  11.  * publicity relating to the software without the specific, prior written
  12.  * permission of Sam Leffler and Silicon Graphics.
  13.  * 
  14.  * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
  15.  * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
  16.  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
  17.  * 
  18.  * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
  19.  * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
  20.  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  21.  * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
  22.  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
  23.  * OF THIS SOFTWARE.
  24.  */
  25. #include "Types.h"
  26.  
  27. #include <stdlib.h>
  28. #include <time.h>
  29. #include <ctype.h>
  30.  
  31. #define    TM_YEAR_BASE    1900        // tm times all start here...
  32. #define    EPOCH_YEAR    1970
  33. #define    EPOCH_WDAY    4        // January 1, 1970 was a Thursday
  34. #define    DAYSPERWEEK    7
  35.  
  36. #define HOUR        100        // 1 hour (using military time)
  37. #define HALFDAY        (12 * HOUR)    // half a day (12 hours)
  38. #define FULLDAY        (24 * HOUR)    // a full day (24 hours)
  39.  
  40. #define    isLeapYear(y)    (((y) % 4) == 0 && ((y) % 100) != 0 || ((y) % 400) == 0)
  41.  
  42. #define    streq(a,b,n)    (strncasecmp(a,b,n) == 0)
  43.  
  44. static    fxBool checkDay(const char*& cp, int& day);
  45. static    void adjustDay(struct tm& at, int day, const struct tm&);
  46. static    fxBool checkMonth(const char*& cp, int& month);
  47. static    fxBool parseMonthAndYear(const char*&, const struct tm& ref,
  48.         struct tm& at, char* emsg);
  49. static    fxBool parseMultiplier(const char* cp, struct tm& at, char* emsg);
  50. static    const char* whitespace(const char* cp);
  51. static    void fixup(struct tm& at);
  52.  
  53. static    void _atSyntax(char* emsg, const char* fmt, ...);
  54. static    void _atError(char* emsg, const char* fmt, ...);
  55.  
  56. static int
  57. operator<(const struct tm& a, const struct tm& b)
  58. {
  59.     return (
  60.     (a.tm_year < b.tm_year)
  61.      || (a.tm_year == b.tm_year && a.tm_yday < b.tm_yday)
  62.      || (a.tm_year == b.tm_year && a.tm_yday == b.tm_yday &&
  63.      a.tm_hour < b.tm_hour)
  64.      || (a.tm_year == b.tm_year && a.tm_yday == b.tm_yday &&
  65.      a.tm_hour == b.tm_hour && a.tm_min < b.tm_min)
  66.     );
  67. }
  68.  
  69. /*
  70.  * Parse at-style time [date] [+increment] syntax
  71.  * and return the resultant time, relative to the
  72.  * specified reference time.
  73.  */
  74. extern "C" int
  75. parseAtSyntax(const char* s, const struct tm& ref, struct tm& at0, char* emsg)
  76. {
  77.     struct tm at = ref;
  78.  
  79.     /*
  80.      * Time specification.
  81.      */
  82.     const char* cp = s = whitespace(s);
  83.     int v = 0;
  84.     if (isdigit(cp[0])) {
  85.     do
  86.         v = v*10 + (*cp - '0');
  87.     while (isdigit(*++cp));
  88.     if (cp-s < 3)            // only hours specified
  89.         v *= HOUR;
  90.     if (cp[0] == ':') {        // HH::MM notation
  91.         if (isdigit(cp[1]) && isdigit(cp[2])) {
  92.         int min = 10*(cp[1]-'0') + (cp[2]-'0');
  93.         if (min >= 60) {
  94.             _atError(emsg, "Illegal minutes value %u", min);
  95.             return (FALSE);
  96.         }
  97.         v += min;
  98.         cp += 3;
  99.         } else {
  100.         _atSyntax(emsg, "expecting HH:MM");
  101.         return (FALSE);
  102.         }
  103.     }
  104.     cp = whitespace(cp);
  105.     if (streq(cp, "am", 2)) {
  106.         if (v >= HALFDAY+HOUR) {
  107.         _atError(emsg, "%u:%02u is not an AM value", v/HOUR, v%HOUR);
  108.         return (FALSE);
  109.         }
  110.         if (HALFDAY <= v && v < HALFDAY+HOUR)
  111.         v -= HALFDAY;
  112.         cp += 2;
  113.     } else if (streq(cp, "pm", 2)) {
  114.         if (v >= HALFDAY+HOUR) {
  115.         _atError(emsg, "%u:%02u is not a PM value", v/HOUR, v%HOUR);
  116.         return (FALSE);
  117.         }
  118.         if (v < HALFDAY)
  119.         v += HALFDAY;
  120.         cp += 2;
  121.     }
  122.     } else {
  123.     if (streq(cp, "noon", 4)) {
  124.         v = HALFDAY;
  125.         cp += 4;
  126.     } else if (streq(cp, "midnight", 8)) {
  127.         v = 0;
  128.         cp += 8;
  129.     } else if (streq(cp, "now", 3)) {
  130.         v = at.tm_hour*HOUR + at.tm_min;
  131.         cp += 3;
  132.     } else if (streq(cp, "next", 4)) {
  133.         v = at.tm_hour*HOUR + at.tm_min;
  134.         cp += 4;
  135.     } else {
  136.         _atSyntax(emsg, "unrecognized symbolic time \"%s\"", cp);
  137.         return (FALSE);
  138.     }
  139.     }
  140.     if ((unsigned) v >= FULLDAY) {
  141.     _atError(emsg, "Illegal time value; out of range");
  142.     return (FALSE);
  143.     }
  144.     at.tm_hour = v/HOUR;
  145.     at.tm_min = v%HOUR;
  146.     at.tm_sec = 0;            // NB: no way to specify seconds
  147.  
  148.     /*
  149.      * Check for optional date.
  150.      */
  151.     cp = whitespace(cp);
  152.     if (checkMonth(cp, v)) {
  153.     at.tm_mon = v;
  154.     if (!parseMonthAndYear(cp, ref, at, emsg))
  155.         return (FALSE);
  156.     } else if (checkDay(cp, v)) {
  157.     adjustDay(at, v, ref);
  158.     } else {
  159.     if (streq(cp, "today", 5)) {
  160.         cp += 5;
  161.     } else if (streq(cp, "tomorrow", 8)) {
  162.         at.tm_yday++;
  163.         cp += 8;
  164.     } else if (cp[0] != '\0' && cp[0] != '+') {
  165.         _atSyntax(emsg, "expecting \"+\" after time");
  166.         return (FALSE);
  167.     }
  168.     /*
  169.      * Adjust the date according to whether it is before ``now''.
  170.      */
  171.     if (at < ref)
  172.         at.tm_yday++;
  173.     }
  174.     /*
  175.      * Process any increment expression.
  176.      */
  177.     if (cp[0] == '+' && !parseMultiplier(++cp, at, emsg))
  178.     return (FALSE);
  179.     fixup(at);
  180.     if (at < ref) {
  181.     _atError(emsg, "Invalid date/time; time must be in the future");
  182.     return (FALSE);
  183.     }
  184.     at0 = at;
  185.     return (TRUE);
  186. }
  187.  
  188. /*
  189.  * Return a pointer to the next non-ws
  190.  * item in the character string.
  191.  */
  192. static const char*
  193. whitespace(const char* cp)
  194. {
  195.     while (isspace(*cp))
  196.     cp++;
  197.     return (cp);
  198. }
  199.  
  200. #define    N(a)    (sizeof (a) / sizeof (a[0]))
  201.  
  202. /*
  203.  * Check for a day argument and, if found,
  204.  * return the day number [0..6] and update
  205.  * the character pointer.
  206.  */
  207. static fxBool
  208. checkDay(const char*& cp, int& day)
  209. {
  210.     static const char* days[] = {
  211.     "sunday", "monday", "tuesday", "wednesday",
  212.     "thursday", "friday", "saturday"
  213.     };
  214.     for (u_int i = 0; i < N(days); i++) {
  215.     u_int len = strlen(days[i]);
  216. again:
  217.     if (streq(cp, days[i], len)) {
  218.         cp += len;
  219.         day = i;
  220.         return (TRUE);
  221.     }
  222.     if (len != 3) {            // an Algol-style for loop...
  223.         len = 3;
  224.         goto again;
  225.     }
  226.     }
  227.     return (FALSE);
  228. }
  229.  
  230. /*
  231.  * Adjust tm_yday according to whether or not the
  232.  * specified day [0...6] is before or after the
  233.  * current week day.  Note that if they are the
  234.  * same day, then the specified day is assumed to
  235.  * in the next week.  This routine assumes that
  236.  * tm_wday is correct; which is only true when
  237.  * setup from ``now''.
  238.  */
  239. static void
  240. adjustDay(struct tm& at, int day, const struct tm& ref)
  241. {
  242.     if (at.tm_wday < day)
  243.     at.tm_yday += day - at.tm_wday;
  244.     else if (at.tm_wday > day)
  245.     at.tm_yday += (DAYSPERWEEK - at.tm_wday) + day;
  246.     else if (at < ref)
  247.     at.tm_yday += DAYSPERWEEK;
  248. }
  249.  
  250. static const char* months[] = {
  251.     "January", "February", "March", "April", "May", "June", "July",
  252.     "August", "September", "October", "November", "December"
  253. };
  254.  
  255. /*
  256.  * Check for a month parameter and if found,
  257.  * return the month index [0..11] and update
  258.  * the character pointer.
  259.  */
  260. static fxBool
  261. checkMonth(const char*& cp, int& month)
  262. {
  263.     for (u_int i = 0; i < N(months); i++) {
  264.     u_int len = strlen(months[i]);
  265. again:
  266.     if (streq(cp, months[i], len)) {
  267.         cp += len;
  268.         month = i;
  269.         return (TRUE);
  270.     }
  271.     if (len != 3) {            // an Algol-style for loop...
  272.         len = 3;
  273.         goto again;
  274.     }
  275.     }
  276.     return (FALSE);
  277. }
  278.  
  279. /*
  280.  * The number of days in each month of the year.
  281.  */
  282. static const u_int nonLeapYear[12] =
  283.     { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  284. static const u_int leapYear[12] =
  285.     { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  286. static const u_int* daysInMonth[2] = { nonLeapYear, leapYear };
  287.  
  288. static void
  289. adjustYDay(struct tm& t)
  290. {
  291.     // adjust year day according to month
  292.     const u_int* days = daysInMonth[isLeapYear(t.tm_year)];
  293.     t.tm_yday = t.tm_mday;
  294.     for (u_int i = 0; i < t.tm_mon; i++)
  295.     t.tm_yday += days[i];
  296. }
  297.  
  298. /*
  299.  * Parse an expected day [, year] expression.
  300.  */
  301. static fxBool
  302. parseMonthAndYear(const char*& cp, const struct tm& ref, struct tm& at, char* emsg)
  303. {
  304.     char* tp;
  305.     at.tm_mday = (u_int) strtoul(cp, &tp, 10);
  306.     if (tp == cp) {
  307.     _atSyntax(emsg, "missing or invalid day of month");
  308.     return (FALSE);
  309.     }
  310.     at.tm_mday--;            // tm_mday is [0..31]
  311.     tp = (char*) whitespace(tp);
  312.     if (*tp == ',') {            // ,[ ]*YYYY
  313.     char* xp;
  314.     u_int year = (u_int) strtoul(++tp, &xp, 10);
  315.     if (tp == xp) {
  316.         _atSyntax(emsg, "missing year");
  317.         return (FALSE);
  318.     }
  319.     if (year < TM_YEAR_BASE) {
  320.         _atError(emsg, "Sorry, cannot handle dates before %u",
  321.         TM_YEAR_BASE);
  322.         return (FALSE);
  323.     }
  324.     at.tm_year = year - TM_YEAR_BASE;
  325.     adjustYDay(at);
  326.     tp = xp;
  327.     } else {                // implicit year
  328.     /*
  329.      * If the specified date is before ``now'', then it
  330.      * must be for next year.  Note that we have to do
  331.      * this in two steps since yday can vary because of
  332.      * leap years.
  333.      */
  334.     adjustYDay(at);
  335.     if (at.tm_yday < ref.tm_yday ||
  336.       (at.tm_yday == ref.tm_yday && at.tm_hour < ref.tm_hour)) {
  337.         at.tm_year++;
  338.         adjustYDay(at);
  339.     }
  340.     }
  341.     const u_int* days = daysInMonth[isLeapYear(at.tm_year)];
  342.     if (at.tm_mday > days[at.tm_mon]) {
  343.     _atError(emsg, "Invalid day of month, %s has only %u days",
  344.         months[at.tm_mon], days[at.tm_mon]);
  345.     return (FALSE);
  346.     }
  347.     cp = tp;
  348.     return (TRUE);
  349. }
  350.  
  351. /*
  352.  * Parse an expected multiplier expression.
  353.  */
  354. static fxBool
  355. parseMultiplier(const char* cp, struct tm& at, char* emsg)
  356. {
  357.     cp = whitespace(cp);
  358.     if (!isdigit(cp[0])) {
  359.     _atSyntax(emsg, "expecting number after \"+\"");
  360.     return (FALSE);
  361.     }
  362.     int v = 0;
  363.     for (; isdigit(*cp); cp++)
  364.     v = v*10 + (*cp - '0');
  365.     cp = whitespace(cp);
  366.     if (*cp == '\0') {
  367.     _atSyntax(emsg, "\"+%u\" without unit", v);
  368.     return (FALSE);
  369.     }
  370.     if (streq(cp, "minute", 6))
  371.     at.tm_min += v;
  372.     else if (streq(cp, "hour", 4))
  373.     at.tm_hour += v;
  374.     else if (streq(cp, "day", 3))
  375.     at.tm_yday += v;
  376.     else if (streq(cp, "week", 4))
  377.     at.tm_yday += DAYSPERWEEK*v;
  378.     else if (streq(cp, "month", 5)) {
  379.     at.tm_mon += v;
  380.     while (at.tm_mon >= 11)
  381.         at.tm_mon -= 11, at.tm_year++;
  382.     adjustYDay(at);
  383.     } else if (streq(cp, "year", 4))
  384.     at.tm_year += v;
  385.     else {
  386.     _atError(emsg, "Unknown increment unit \"%s\"", cp);
  387.     return (FALSE);
  388.     }
  389.     return (TRUE);
  390. }
  391.  
  392. /*
  393.  * Correct things in case we've slopped over as a result
  394.  * of an increment expression or similar calculation. 
  395.  * Note that this routine ``believes'' the minutes, hours,
  396.  * year, and yday values and recalculates month, mday,
  397.  * and wday values based on the believed values.
  398.  */
  399. static void
  400. fixup(struct tm& at)
  401. {
  402.     while (at.tm_min >= HOUR)
  403.     at.tm_hour++, at.tm_min -= HOUR;
  404.     while (at.tm_hour >= FULLDAY)
  405.     at.tm_yday++, at.tm_hour -= FULLDAY;
  406.     fxBool leap;
  407.     int daysinyear;
  408.     for (;;) {
  409.     leap = isLeapYear(at.tm_year);
  410.     daysinyear = leap ? 366 : 365;
  411.     if (at.tm_yday < daysinyear)
  412.         break;
  413.     at.tm_yday -= daysinyear, at.tm_year++;
  414.     }
  415.     /*
  416.      * Now recalculate derivative values
  417.      * to insure everything is consistent.
  418.      */
  419.     const u_int* days = daysInMonth[leap];
  420.     at.tm_mday = at.tm_yday;
  421.     for (at.tm_mon = 0; at.tm_mday >= days[at.tm_mon]; at.tm_mon++)
  422.     at.tm_mday -= days[at.tm_mon];
  423.     at.tm_mday++;            // NB: [1..31]
  424.  
  425.     int eday = at.tm_yday;
  426.     for (u_int year = EPOCH_YEAR - TM_YEAR_BASE; year < at.tm_year; year++)
  427.     eday += isLeapYear(year) ? 366 : 365;
  428.     at.tm_wday = (EPOCH_WDAY + eday) % DAYSPERWEEK;
  429. }
  430.  
  431. #include <stdarg.h>
  432.  
  433. static void
  434. _atSyntax(char* emsg, const char* fmt, ...)
  435. {
  436.     if (emsg != NULL) {
  437.     strcpy(emsg, "Syntax error, ");
  438.     va_list ap;
  439.     va_start(ap, fmt);
  440.     vsprintf(strchr(emsg, '\0'), fmt, ap);
  441.     va_end(ap);
  442.     }
  443. }
  444.  
  445. static void
  446. _atError(char* emsg, const char* fmt, ...)
  447. {
  448.     if (emsg != NULL) {
  449.     va_list ap;
  450.     va_start(ap, fmt);
  451.     vsprintf(emsg, fmt, ap);
  452.     va_end(ap);
  453.     }
  454. }
  455.